#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include <math.h>
#include <set>
#include <map>
#include <omp.h>
#include <algorithm>
#include "/usr/lib/openmpi/include/mpi.h"
#include <unistd.h>


using namespace std;


static int num_char = 5;

set <string> dici;
map <int, int> histo;
//pair< set<int>::iterator, bool > ret;

// Deixa as letras todas minusculas de uma string 
string toLowerCase(string s) {
    for (int i = 0; i < s.size(); i++)
        s[i] = tolower(s[i]);
    return s;
}

// Limpa a string de apontuacao
void remove_pontuacion(string& in) {
    for (int i = 0; i < in.length(); i++) {
        // ispunct é uma funcao da ctype.h que detecta se é alguma potuacao ja predefinida
        if (in[i] < 97 || in[i] > 122 || ispunct(in[i]))
            in[i] = ' '; // troca a pontuacao por espaco em branco
    }
    return;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Quebra

/* Esta função quebra uma palavra com tamanho maior 
 * que 5 em palavras com tamanho 5 ou menor
*/ 
string quebra(string& in) {
    string out;

    for(int i = 0; i < in.size(); i++) {
        out += in[i];
        if( (i+1) % 4 == 0 ) out += " ";
    }

    return out;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// TOKEN QUEBRA A STRING LINHA EM STRINGS MENORES, SEPARADAS POR ESPAÇO, SOMENTE PALAVRAS

// Dado uma linha do arquivo (in), quebra em palavras (palavras entre os espacos brancos)
// E verifica se é palindromo ou nao, se for verifica se é primo tambem.
void token(string& in) {
    istringstream iss(in);

    do {
        string sub;
        iss >> sub;

        if(sub.size() < 2) continue;

        if( sub.size() > num_char )  {
            istringstream iss_sub(quebra(sub));

            do {
                string sub2;
                iss_sub >> sub2;
                sub2 = toLowerCase(sub2); // tudo caixa baixa

                if( sub2.size() >= 2  && dici.insert( sub2 ).second ) histo[sub2.size()]++; //insere no dicionario

            } while(iss_sub);

        } else {
            sub = toLowerCase(sub); // tudo caixa baixa
            if( dici.insert( sub ).second ) histo[sub.size()]++; // insere no dicionario a palavra

        }
    } while (iss); //ate as palavras acabarem
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Monta

/* Esta função pega todas as palavras que não são separadas por espaço 
 * presentes no texto e insere num set de strings chamado dici
*/
void monta(string& in){
    istringstream iss(in);
    string s;						//Declara variavel s, que receberá as linhas lidas do texto
    

    do{
        iss >> s;
        s = toLowerCase(s); // tudo caixa baixa
        remove_pontuacion(s);
        token(s);
    } while ( iss );


}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// PALAVRA

/* Esta função gera aleatóriamente um conjunto de 2 caracteres
*/
string palavra(){
    string out;
    char ale;
    int tam;
    // 0 1 2 3
    tam = (int)rand()%(num_char-1) + 2;			//gera tamanho dapalavra aleatóriamente entre 2 e num_char

    if(!histo[tam]) return out;

    //Monta a palavra
    for(int i=0; i<tam; i++){
        ale = (int)rand()% 26 + 97;		//gera letra aleatória
        out += ale;     				//insere letra aleatória no vetor
    }

    return out;					//retorna a string
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//Is_word

/* Esta função recebe uma string e verifica se está no dici
*/

bool is_word(string s){

    if(binary_search(dici.begin(),dici.end(), s)){return true;}	//retorna um se encontrou a string
    else{return false;}						//retorna 0  se não encontrou a string no vetor
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////




//Main 
int main (int argc, char ** argv){
    MPI::Init(argc, argv);
    int processRun = 1 , enviar = 0, receber = 0  ;

    ostringstream *  oss = new ostringstream();
    istringstream *  ississ = new istringstream();
#pragma omp parallel default(shared) num_threads(2)
    {
        double       inittime,totaltime, iniGeralTime;
        int numberNodes, myNode , total, myThread ;
        MPI::Status status;
        int contador = 0, contador1 = 0 ;
        int countP;

        iniGeralTime = inittime = MPI::Wtime();
        numberNodes = MPI::COMM_WORLD.Get_size();
        myNode = MPI::COMM_WORLD.Get_rank();
        myThread = omp_get_thread_num();



        if( myNode == 0  && myThread == 0 ){


            cout <<  "Iniciando Processo 0"<< endl;
            // vamos abrir o arquivo para leitura no modo binário
            FILE *arquivo = fopen("palavras.txt", "rb");
            if(arquivo != NULL){
                // vamos obter o tamanho do arquivo em bytes
                fseek(arquivo, 0, SEEK_END);
                long tam_arquivo = ftell(arquivo);
                rewind(arquivo);

                // vamos alocar memória para todo o conteúdo do arquivo
                char *buffer = (char*)malloc(sizeof(char) * tam_arquivo);
                // a memória foi alocada com sucesso?
                if(buffer != NULL){
                    // vamos copiar o conteúdo do arquivo para o buffer
                    size_t resultado = fread(buffer, 1, tam_arquivo, arquivo);

                    // vamos verificar se a operação foi feita com sucesso
                    if(resultado == tam_arquivo){
                        puts("O conteudo do arquivo foi lido com sucesso\n\n");


                        for(int j = 1 ; j <  (numberNodes); j++ ){
                            cout <<  "Enviando Mensagem  a "<< j << endl;
                            MPI::COMM_WORLD.Send((char*)buffer , resultado,MPI::CHAR, j, 2);

                        }



                        string s;
                        string block(buffer);
                        monta(block);					    //le o texto e monta set dici
                        free(buffer); // libera a memória do buffer


                        total = dici.size();
                        int j = 1, contPrint = 0 ;


                        {
                            while( !dici.empty() ){


                                // Busca sobre informacoes das mensagens
                                if( MPI::COMM_WORLD.Iprobe(j, 3,status) ){

                                    unsigned int number_amount;


                                    //Encontra o numero de caracteres da mensagem
                                    number_amount = status.Get_count(MPI::CHAR);


                                    // aloca um buffer para a mensagem
                                    char* buf = (char*)calloc(number_amount+3 ,sizeof(char) );
                                    if(buf == NULL)
                                        continue;
                                    // recebe a mensagem
                                    MPI::COMM_WORLD.Recv(buf, number_amount, MPI::CHAR, j, 3, status);
                                    //cout << number_amount << " Encontrado por "<< j << endl;
                                    number_amount = 0;
                                    istringstream iss(buf);
                                    if(iss)
                                        if(number_amount != 0 )
                                            for(int m = 1 ; m <  (numberNodes); m++ ){
                                                if(m != j ){
                                                    // cout << myNode <<" Enviando buf"  <<  endl;
                                                    MPI::COMM_WORLD.Send((char*)buf , number_amount,MPI::CHAR, m, 4);
                                                }
                                            }
                                    do{
                                        iss >> s;


                                        if(s.empty()) continue;

                                        if(is_word(s)){
                                            //verifica se a palavra está no texto



                                            histo[s.size()]--;
                                            dici.erase(s);

                                            //cout << myNode <<" excluindo "<< s << " Encontrado por "<< j << endl;

                                            double parcial  = MPI::Wtime() - iniGeralTime;
                                            countP == 100- 100 *  dici.size()/total?   :cout << total - dici.size() << "/" << total << " palavaras encontradas - " <<  (countP = 100- 100 *  dici.size()/total)  << " % concluido, em   "<<parcial <<" segundos ..." << endl;

                                        }


                                        s.clear();				     //limpa string para receber nova palavra



                                    }while(iss);

                                    if(buf != NULL){
                                        free(buf);
                                        buf ==NULL;
                                    }
                                }
                                j++;
                                if(j == numberNodes)j=1;
                            }

                        }

                    }

                }

                fclose(arquivo); // fecha o arquivo


            }
            for(int m = 1 ; m <  (numberNodes); m++ ){
                int sinal = -1;
                MPI::COMM_WORLD.Send(&sinal, 1,MPI::INT, m, 5);
            }
            processRun = 0;
#pragma omp flush (processRun)
            double parcial  = MPI::Wtime() - iniGeralTime;
            cout << total - dici.size() << " palavaras encontradas - " << 100 << " %  concluido, em   "<< parcial <<" segundos ..." << endl;
        }else if(  myNode == 0 &&  myThread == 1  ){

            while(processRun == 1)
                usleep((5000));



        }else if(   myNode != 0 &&  myThread == 0  ){
            ///
            ///Processos diferentes do 0
            ///
            ///
            // Busca sobre informacoes das mensagens
            MPI::COMM_WORLD.Probe(0, 2 ,status) ;
            unsigned int number_amount;
            //Encontra o numero de caracteres da mensagem
            number_amount = status.Get_count(MPI::CHAR);
            // aloca um buffer para a mensagem
            char* buf = (char*)calloc( 3+ number_amount, sizeof(char));
            inittime = MPI::Wtime();
            // recebe a mensagem
            MPI::COMM_WORLD.Recv(buf, number_amount, MPI::CHAR, 0, 2, status);
            totaltime = MPI::Wtime() - inittime;
            char name[60];
            int resultlen;
            MPI::Get_processor_name(name, resultlen);




            string s;
            string block(buf);
            if(buf != NULL){
                free(buf);
                buf ==NULL;
            }
            monta(block);					    //le o texto e monta set dici
            srand(myNode);					    //libera semente para o random
            ostringstream *  ossbuf = new ostringstream() ;


            cout <<  "Iniciando process "<< myNode << endl;


            while(processRun == 1){
                s = palavra();
                contador ++; contador1 ++;
                // if(s.size() == 5) cout << s << endl;
                if(s.empty()) continue;

                if(is_word(s) ){//verifica se a palavra está no texto
                    histo[s.size()]--;
                    dici.erase(s);
                    (*ossbuf)  << s << " ";
                }


                if(receber == 1){
                    string ss;
                    do{
                        *ississ >> ss;

                        //cout << myNode <<" recebido "<< ss << endl;
                        if(ss.empty() || ss.size() < 2) continue;

                        if(is_word(ss)){				     //verifica se a palavra está no texto
                            histo[ss.size()]--;
                            //cout <<"É palavra: "<< s;
                            dici.erase(ss);
                            //cout << myNode <<" excluindo "<< ss << endl;
                        }

                        ss.clear();
                    }while(*ississ);
                    receber = 0;
                    delete ississ;
                    ississ = NULL;
#pragma omp flush(receber, ississ)
                }



                if(contador > 1000 && enviar == 0){
                    oss =   new ostringstream(ossbuf->str());
                    contador = 0 ;
                    delete ossbuf;
                    ossbuf = new ostringstream();
                    enviar =  1;
#pragma omp flush(enviar, ossbuf)
                }

                if(contador1 > 10000  ){

                    contador1 = 0;
                }
                s.clear();				     //limpa string para receber nova palavra
            }


        }else if(   myNode != 0 &&  myThread == 1   ){

            while(enviar  == 0)usleep(2000);
            while(processRun == 1){




                if(enviar == 1 && processRun == 1){
                     //cout << myNode <<" Enviando oss"  <<  endl;

                    if(oss != NULL)
                        if(oss->str().size() != 0){
                            MPI::COMM_WORLD.Send( oss->str().c_str(),oss->str().size() ,MPI::CHAR, 0, 3);
                            delete oss;
                            oss = NULL;
                        }
                     enviar = 0 ;
                }
                if
                 (  MPI::COMM_WORLD.Iprobe(0, 4 ,status))
                // ;
                 {
                     unsigned int number_amount1;
                     //Encontra o numero de caracteres da mensagem
                     number_amount1 = status.Get_count(MPI::CHAR);

                     //cout << myNode <<" number > 0 "<< endl;
                     // aloca um buffer para a mensagem
                     char* buf = (char*)calloc(number_amount1 + 3 ,sizeof(char) );
                     if(buf == NULL)
                         continue;
                     // recebe a mensagem
                     MPI::COMM_WORLD.Recv(buf, number_amount1, MPI::CHAR, 0, 4, status);
                     ississ = new istringstream (buf);


                     if(buf != NULL){
                         free(buf);
                         buf ==NULL;
                     }
                     receber = 1 ;
 #pragma omp flush(receber,ississ )
                 }

                if( MPI::COMM_WORLD.Iprobe(0, 5 ,status)){
                    int test;
                    MPI::COMM_WORLD.Recv(&test, 1, MPI::INT, 0, 5, status);
                    if(test == -1){
                        processRun = 0;

                        #pragma omp flush(processRun )
                        break;
                    }

                }
                 usleep(2000);
            }

        }




    }





    MPI::Finalize();

    return 0;
}
